<?php
	class RoutePattern {
		private $pattern = array();
		private $defaults = array();
		private $rules = array();
		
		public function __construct($pattern) {
			$this->pattern = $this->explodePath($pattern);
		}
		
		public function extractParameters($path) {
			$parameters = $this->explodePath($path);
			$max = count($parameters);
			if ($max > count($this->pattern)) {
				return false;
			}
			$extracted = $this->defaults;
			foreach ($this->pattern as $index => $part) {
				$isLastPart = $max <= $index;
				$name = self::getPlaceholderName($part);
				if($name !== false) {
					if(!$isLastPart) {
						$value = $parameters[$index];
						if(!$this->ruleIsSatisfied($name, $value)) {
							return false;
						}
						$extracted[$name] = $value;
					} elseif(!isset($this->defaults[$name])) {
						return false;
					}
				} elseif($isLastPart || $part !== $parameters[$index]) {
					return false;
				}
			}
			return $extracted;
		}
		
		private function explodePath($path) {
			$path = trim($path, '/');
			return ($path != '') ? explode('/', str_replace('.', '/.', $path)) : array();
		}
		
		public static function getPlaceholderName($part) {
			return ($part[0] == ':') ? substr($part, 1) : false;
		}
		
		private function ruleIsSatisfied($name, $value) {
			if (isset($this->rules[$name])) {
				return preg_match($this->rules[$name], $value);
			} else return true;
		}
		
		public function createPath($parameters) {
			$path = array();
			$usedValues = $this->defaults;
			$exactPartWasAdded = false;
			foreach(array_reverse($this->pattern) as $part) {
				$name = self::getPlaceholderName($part);
				if($name !== false) {
					if(isset($parameters[$name])) {
						$usedValues[$name] = $value = $parameters[$name];
						if(!$this->ruleIsSatisfied($name, $value)) {
							return false;
						}
						$path[] = $value;
					} elseif(isset($this->defaults[$name])) {
						if($exactPartWasAdded) {
							$path[] = $this->defaults[$name];
						}
					} else {
						return false;
					}
					
				} else {
					$path[] = $part;
					$exactPartWasAdded = true;
				}
			}
			if(count(array_diff_assoc($parameters, $usedValues))) {
				return false;
			}
			return str_replace('/.', '.', implode('/', array_reverse($path)));
		}
        
		public function hasHigherPriorityThan(RoutePattern $other) {
			return $this->getPriority() > $other->getPriority();
		}
		
		protected function getPriority() {
			$priority = 0;
			foreach ($this->pattern as $part) {
				if($part[0] == ':') break;
				$priority++;
			}
			return $priority;
		}
		
		public function setRegexRuleFor($name, $regex) {
			$this->rules[$name] = $regex;
		}
		
		public function setDefaultFor($name, $value) {
			$this->defaults[$name] = $value;
		}
	}
?>